package gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import world.*;

import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class WorldViewPanel extends JPanel {

	private CritterWorld world;
	private Hex hexSelected;
	private Location locSelected;
	private Critter critSelected;
	
	private final Color defaultColor = new Color(128,220,255);
	private final Color plantColor = Color.green;
	private final Color rockColor = Color.black;
	private final Color foodColor = new Color(135, 58, 0);
	private final Color selectedRockColor = new Color(89, 31, 24);
	private final Color selectedPlantColor = new Color(16, 175, 12);
	private final Color selectedEmptyColor = new Color(60, 160, 230);
	
	/**The side length of a hexagon in pixels (half the width)*/
	public final int S = 30;
	/**Half the height of a hexagon in pixels*/
	public final int R = 26;
	/**One quarter of the width of a hexagon in pixels*/
	public final int H = S / 2;
	

	public Dimension getMinimumSize() {
		return new Dimension((H + S)*(world.MAX_COLUMN + 1) + H, 2*R*(world.ROWS_PER_COL) + R + 1);
	}
	
	public Dimension getPreferredSize() {
		return getMinimumSize();
	}
	
	/**
	 * Returns the Location of the Hex that has most recently been clicked on.
	 */
	public Location getSelectedLoc() {
		return locSelected;
	}
	
	/**
	 * Returns the Hex that has most recently been clicked on. May be null.
	 */
	public Hex getSelectedHex() {
		return hexSelected;
	}

	/**
	 * Returns the Critter in the Hex that has most recently been clicked on. May be null.
	 */
	public Critter getSelectedCritter() {
		return critSelected;
	}
	
	
	public WorldViewPanel(CritterWorld w){
		world = w;
		addMouseListener(new CritterClickListener());
	}

	public void paintComponent(Graphics g){
		g.setColor(Color.GRAY);
		g.fillRect(0, 0, getWidth(), getHeight());
		g.setColor(Color.BLACK);
		Rectangle view = getVisibleRect();
		int startCol = Math.max(view.x/(S+H) - 1, 0);
		int endCol = Math.min((view.x + view.width)/(S+H), world.MAX_COLUMN);
		int startRow = Math.min((view.y + view.height)/(2*R) + 1, world.ROWS_PER_COL);
		int endRow = Math.max(view.y/(2*R), 1);
		for(int c = startCol; c <= endCol; c++){
			for(int r = startRow; r >= endRow; r--){//relative row
				int row = world.ROWS_PER_COL - r + (c+1)/2;
				Hex hexDrawn = world.getHex(new Location(world, row, c));
				if(c % 2 == 0)
					drawOneHex(g, hexDrawn, (c*(S+H) + H), r*2*R + R);
				else
					drawOneHex(g, hexDrawn, (c*(S+H) + H), r*2*R);
			}
		}
		if(critSelected != null && critSelected.getMemValue(4) < 0)
			critSelected = null;
		if (critSelected != null) {
			hexSelected = critSelected.getHex();
			locSelected = hexSelected.getLocation();
		} if (hexSelected != null) {
			drawSelectedHex(g);
		}
	}
	
	private void drawSelectedHex(Graphics g) {
		int c = locSelected.getCol();
		int startX = (c*(S+H) + H);
		int row = locSelected.getRow();
		int r = world.ROWS_PER_COL - row + (c+1)/2;
		int startY;
		if (c % 2 == 0) startY = r*2*R + R;
		else startY = r*2*R;
		drawOneHex(g, Color.WHITE, startX, startY);
		if (hexSelected.isRock()) {
			drawOneHex(g, selectedRockColor, startX, startY);
		} else if (hexSelected.hasPlant()) {
			drawOneHex(g, selectedPlantColor, startX, startY);
		} else {
			drawOneHex(g, selectedEmptyColor, startX, startY);
		}
		if (hexSelected.getFood() > 0)
			drawFood(g, foodColor, startX, startY);
		if (hexSelected.hasCritter())
			drawCritter(g, hexSelected.getCritter(), startX, startY);
	}
	
	/**
	 * Draws one Hex, starting with the bottom-left corner, and fills it with a Critter
	 * or a rock or a plant etc. (if needed)
	 */
	private void drawOneHex(Graphics g, Hex hexToDraw, int startX, int startY){
		if(hexToDraw.isRock())
			drawOneHex(g, rockColor, startX, startY);
		else{
			if (hexToDraw.hasPlant())
				drawOneHex(g, plantColor, startX, startY);
			else
				drawOneHex(g, defaultColor, startX, startY);
			if(hexToDraw.getFood() > 0)
				drawFood(g, foodColor, startX, startY);
			if(hexToDraw.hasCritter())
				drawCritter(g, hexToDraw.getCritter(), startX, startY);
		}
	}

	/**
	 * Draws one complete hexagon, starting with the bottom-left corner
	 */
	private void drawOneHex(Graphics g, Color fillColor, int startX, int startY){
		int[] xPoints = new int[6];
		xPoints[0] = startX;
		xPoints[1] = xPoints[0] + S;
		xPoints[2] = xPoints[1] + H;
		xPoints[3] = xPoints[2] - H;
		xPoints[4] = xPoints[3] - S;
		xPoints[5] = xPoints[4] - H;

		int[] yPoints = new int[6];
		yPoints[0] = startY;
		yPoints[1] = yPoints[0];
		yPoints[2] = yPoints[1] - R;
		yPoints[3] = yPoints[2] - R;
		yPoints[4] = yPoints[3];
		yPoints[5] = yPoints[4] + R;
		
		g.setColor(fillColor);
		g.fillPolygon(xPoints, yPoints, 6);
		
		g.setColor(Color.black);
		g.drawPolygon(xPoints, yPoints, 6);
	}
	
	private void drawFood(Graphics g, Color fillColor, int startX, int startY){
		g.setColor(fillColor);
		g.fillOval(startX - H/4, startY - R/2, H/3, H/2);
		g.fillOval(startX + H, startY - H, H/3, R/2);
		g.fillOval(startX + 7*S/8, startY - 8*H/7, H/3, H/4);
		g.fillOval(startX - H/2, startY - 4*R/3, H, S/3);
		g.fillOval(startX + S/3, startY - 7*R/6, H/2, H/2);
		g.fillOval(startX + 5*S/6, startY - 4*R/3, H, H);
		g.fillOval(startX, startY - 15*R/8, 3*S/5, 2*H/3);
	}
	
	private void drawCritter(Graphics g, Critter crit, int startX, int startY){
		int centerX = startX + H;
		int centerY = startY - R;
		int sizeFactor = Math.min(crit.getMemValue(3), 10);
		int legLength = (3*H/2 + (H/5)*(sizeFactor - 1))/2;
		int circleWidth = 4*legLength/3;
		
		g.setColor(Color.black);
		g.drawLine(centerX - legLength, centerY, centerX + legLength, centerY);
		g.drawLine(centerX - legLength/2, centerY - 6*legLength/7, centerX + legLength/2, centerY + 6*legLength/7);
		g.drawLine(centerX + legLength/2, centerY - 6*legLength/7, centerX - legLength/2, centerY + 6*legLength/7);
		
		float redVal = Math.min(1f, crit.getMemValue(2) / (float)world.CRITTER_MAX_OFF);
		float blueVal = Math.min(1f, crit.getMemValue(1) / (float)world.CRITTER_MAX_DEF);
		int rules = (crit.getGenome() == null) ? 0 : crit.getGenome().getChildren().size();
		float greenVal = Math.min(1f, (float)Math.log(1 + rules/50.0));
		
		g.setColor(new Color(redVal, greenVal, blueVal));
		g.fillOval(centerX - circleWidth/2, centerY - circleWidth/2, circleWidth, circleWidth);
		
		g.setColor(Color.black);
		int headWidth = circleWidth/3;
		int headCenterX;
		int headCenterY;
		switch(crit.getDirection()){
		case 1:
			headCenterX = centerX + circleWidth/2;
			headCenterY = centerY - circleWidth/4;
			break;
		case 2:
			headCenterX = centerX + circleWidth/2;
			headCenterY = centerY + circleWidth/4;
			break;
		case 3:
			headCenterX = centerX;
			headCenterY = centerY + 7*circleWidth/12;
			break;
		case 4:
			headCenterX = centerX - circleWidth/2;
			headCenterY = centerY + circleWidth/4;
			break;
		case 5:
			headCenterX = centerX - circleWidth/2;
			headCenterY = centerY - circleWidth/4;
			break;
		default:
			headCenterX = centerX;
			headCenterY = centerY - 7*circleWidth/12;
			break;
		}
		g.fillOval(headCenterX - headWidth/2, headCenterY - headWidth/2, headWidth, headWidth);
	}
	
	/**
	 * Translates the given x and y pixel coordinates into a Location in the CritterWorld
	 * @param x x pixel coordinate
	 * @param y y pixel coordinate
	 * @return the Location corresponding to the given coordinates
	 */
	public Location getLocationFromPixels(int x, int y){
		int secx = x / (S + H);
		int secy = y / (2*R);
		int relx = x - secx*(S + H);
		int rely = y - secy*2*R;
		
		int topRow; 
		int hexType;
		Location loc;
		if(secx % 2 == 0){
			hexType = getEvenType(relx, rely);
			switch(hexType){
			case 1:
				topRow = (secx + world.ROW_COL_EXPR) / 2;
				loc = new Location(world, topRow - secy + 1, secx);
				break;
			case 2:
				topRow = (secx + world.ROW_COL_EXPR) / 2;
				loc = new Location(world, topRow - secy, secx);
				break;
			default:
				topRow = (secx - 1 + world.ROW_COL_EXPR) / 2;
				loc = new Location(world, topRow - secy, secx - 1);
				break;
			}
		} else {
			hexType = getOddType(relx, rely);
			switch(hexType){
			case 1:
				topRow = (secx + world.ROW_COL_EXPR) / 2;
				loc = new Location(world, topRow - secy, secx);
				break;
			case 2:
				topRow = (secx - 1 + world.ROW_COL_EXPR) / 2;
				loc = new Location(world, topRow - secy + 1, secx - 1);
				break;
			default:
				topRow = (secx - 1 + world.ROW_COL_EXPR) / 2;
				loc = new Location(world, topRow - secy, secx - 1);
				break;
			}
		}
		return loc;
	}
	
	/**
	 * Returns the hex type in an even column section based on the given
	 * relative x and y pixel coordinates
	 */
	private int getEvenType(int relx, int rely){
		int type;
		if (rely < R)
			type = (relx > rely / (((double)R)/H)) ? 1 : 3;
		else
			type = (relx > 2*H - rely / (((double)R)/H)) ? 2 : 3;
		return type;
	}
	
	/**
	 * Returns the hex type in an odd column section based on the given
	 * relative x and y pixel coordinates
	 */
	private int getOddType(int relx, int rely){
		int type;
		if (rely < R)
			type = (relx > H - rely / (((double)R)/H)) ? 1 : 2;
		else
			type = (relx > rely / (((double)R)/H) - H) ? 1 : 3;
		return type;
	}
	
	private class CritterClickListener extends MouseAdapter{
		public void mouseClicked(MouseEvent e){
			locSelected = getLocationFromPixels(e.getX(), e.getY());
			hexSelected = world.getHex(locSelected);
			if (hexSelected != null && hexSelected.hasCritter()) {
				critSelected = hexSelected.getCritter();
			}
			if (!hexSelected.hasCritter()) {
				critSelected = null;
			}
			SwingUtilities.getWindowAncestor(getParent()).repaint();
		}
	}
	
	public void changeWorld(CritterWorld w){
		world = w;
		locSelected = null;
		critSelected = null;
		hexSelected = null;
	}
}
